using System.Collections.Generic;
using System.Linq;
using System.Web.Mvc;
using System.Web.Routing;
using Orchard.ContentManagement;
using Orchard.Core.Common.Models;
using Orchard.Core.Contents.Controllers;
using Orchard.Core.Settings.Models;
using Orchard.DisplayManagement;
using Orchard.Localization;
using Orchard.Security;
using Orchard.UI.Notify;
using Orchard.Mvc.Extensions;
using System;
using Orchard.Settings;
using Orchard.UI.Navigation;
using Orchard.UI.Admin;
using Orchard;
using Orchard.Themes;
using System.Text;
using Orchard.Messaging.Services;
using Orchard.Roles.Models;
using Orchard.Data;
using Orchard.FileSystems.Media;
using Rework.CommerceHistory.Models;
using Orchard.Environment.Extensions;
using Nwazet.Commerce.Models;
using Rework.CommerceHistory.Rules;
using System.Net;
using System.IO;

namespace EMS.Cart.Controllers {
    [Themed]
    [OrchardFeature("Rework.CommercePayPal")]
    public class CommercePayPalController : Controller {
        public IOrchardServices Services;
        private readonly IStorageProvider _storageProvider;
        private readonly IMembershipService _membershipService;
        private readonly IMessageManager _messageManager;
        private readonly IRulesManager _rulesManager;

        public CommercePayPalController(
            IOrchardServices services
            , IStorageProvider storageProvider
            , IMembershipService membershipService
            , IMessageManager messageManager
            , IRulesManager rulesManager
            ) {
            Services = services;
            _storageProvider = storageProvider;
            _membershipService = membershipService;
            _messageManager = messageManager;
            _rulesManager = rulesManager;
            
            T = NullLocalizer.Instance;
        }

        public Localizer T { get; set; }
        
        #region PayPal

        /// <summary>
        /// PayPal canceled
        /// </summary>
        /// <returns></returns>
        public ActionResult Canceled() {
            return View();
        }
        
        #region Based on https://mvcsamples.svn.codeplex.com/svn/trunk/Kona.Web/Controllers/PayPalController.cs and https://cms.paypal.com/us/cgi-bin/?cmd=_render-content&content_ID=developer/e_howto_admin_IPNIntro 
        [HttpPost]
        public ActionResult HandleIPN() { // Originally called IPN()
            var formVals = new Dictionary<string, string>();
            formVals.Add("cmd", "_notify-validate");
            
            string fullIPN = Request.Url.Query;
            string isSandbox = Request["test_ipn"];
            string transactionID = Request["txn_id"];
            string payPalReturnedBusinessEmail = Request["receiver_email"];
            string sAmountPaid = Request["mc_gross"];
            var custom = Request["custom"];
            var numberItemsString = Request["num_cart_items"];
            int numberItems = 0;
            Int32.TryParse(numberItemsString, out numberItems);

            // I don't use these - here for completeness
            string firstName = Request["first_name"];
            string lastName = Request["last_name"];
            string payerEmail = Request["payer_email"];
            string street1 = Request["address_street"];
            string city = Request["address_city"];
            string stateOrProvince = Request["address_state"];
            string country = Request["address_country"];
            string zip = Request["address_zip"];

            // Expand the "custom" variable and grab the UserName;
            string userName = "";
            string[] customPairs = custom.Split(';');
            foreach (var customPair in customPairs) {
                string[] nameValue = customPair.Split(':');
                if (nameValue[0].Trim().ToLowerInvariant() == "username") {
                    userName = nameValue[1].Trim();
                    break;
                }
            }

            var customer = _membershipService.GetUser(userName);
            var payPalSettings = Services.WorkContext.CurrentSite.As<CommercePayPalSettingsPart>();
            
            string response = GetPayPalResponse(formVals, isSandbox == "1" ? true : false);

            // Ensure the response is Verified
            if (response == "VERIFIED") {
                string paymentStatus = Request["payment_status"];

                // Ensure the payment status is Completed
                if (paymentStatus == "Completed") {
                    string settingsBusinessEmail = payPalSettings.BusinessEmailAddress;

                    // Ensure the valid payment response email
                    if (settingsBusinessEmail == payPalReturnedBusinessEmail) {

                        // Ensure the transactionId has NOT already been used
                        int transactionIdCount = Services.ContentManager.Query<CommerceHistoryPart, CommerceHistoryPartRecord>()
                            .Where(c => c.ConfirmedTransactionId == transactionID)
                            .Count();

                        if (transactionIdCount == 0) {

                            // Here is the mind map of what needs done:
                            // Update the users cart item:
                            //      PayPalIPN with number from PayPal
                            //      PayPalConfirmedPrice as the amount from PayPal
                            //      If PayPalConfirmedPrice is TotalPrice
                            //          IsConfirmed as True
                            //          Trigger "ProcessingComplete"
                            //      Else
                            //          IsConfirmed as False
                            //          Trigger "ErrorMessage"

                            for (int i = 1; i <= numberItems; i++) { // Iterate through the cart items
                                string itemNumber = Request["item_number" + i.ToString()];
                                string itemName = Request["item_name" + i.ToString()];
                                string quantity = Request["quantity" + i.ToString()];
                                string mcGross = Request["mc_gross_" + i.ToString()];
                                
                                var commerceHistoryContentItem = Services.ContentManager.New("CommerceHistory");
                                var commerceHistoryPart = commerceHistoryContentItem.As<CommerceHistoryPart>();
                                // Load up the variables
                                commerceHistoryPart.UserId = customer.Id;
                                commerceHistoryPart.ProductId = Int32.Parse(itemNumber);
                                commerceHistoryPart.Quantity = Int32.Parse(quantity);
                                commerceHistoryPart.Price = Convert.ToDouble(mcGross);

                                commerceHistoryPart.ConfirmedTransactionId = transactionID;
                                commerceHistoryPart.ConfirmedIPN = fullIPN;

                                var commonPart = commerceHistoryContentItem.As<CommonPart>();
                                string superUserName = Services.WorkContext.CurrentSite.SuperUser;
                                var superUser = _membershipService.GetUser(superUserName);
                                commonPart.Owner = superUser;

                                // Get the actual product to determine whether the price purchased for is valid
                                var productPart = Services.ContentManager.Get<ProductPart>(commerceHistoryPart.ProductId);

                                // Determine whether everything is confirmed
                                bool isConfirmed = false;
                                if (commerceHistoryPart.Price >= productPart.Price) {
                                    isConfirmed = true;
                                }
                                else {
                                    string messageBody = string.Format("Unexpected Pricing : Transaction ID = {0}\n\nHere is the PayPal IPN: {1}",
                                    transactionID
                                    , fullIPN);

                                    _rulesManager.TriggerEvent("CommercePayPal", "ErrorMessage", () => new Dictionary<string, object> { { "Text", messageBody } });
                                }
                                commerceHistoryPart.IsPaymentConfirmed = isConfirmed;

                                Services.ContentManager.Create(commerceHistoryContentItem, VersionOptions.Published);
                                
                                if (isConfirmed) {
                                    _rulesManager.TriggerEvent("CommercePayPal", "ProcessingComplete", () => new Dictionary<string, object> { { "Content", commerceHistoryContentItem } });
                                }
                                else {
                                    _rulesManager.TriggerEvent("CommercePayPal", "ProcessingIncomplete", () => new Dictionary<string, object> { { "Content", commerceHistoryContentItem } });
                                }
                            }
                        }
                        else { // TransactionIdCount > 0
                            // Dangerous area because transactionId has already been used
                            //  Likely a fraud attempt but it could be that the user payed by eCheck (which would send the two notifications)
                            //  Send message off for a human to decide
                            string messageBody = string.Format("Possible Fraud Attempt: Transaction ID = {0}\n\nHere is the PayPal IPN: {1}",
                                transactionID,
                                fullIPN);

                            _rulesManager.TriggerEvent("CommercePayPal", "ErrorMessage", () => new Dictionary<string, object> { { "Text", messageBody } });
                        }

                    }
                    else { // Settings Business Email does not match PayPal Returned Business Email
                        string messageBody = string.Format("Fraud Attempt : Transaction ID = {0}\n\nHere is the PayPal IPN: {1}",
                        transactionID,
                        fullIPN);

                        _rulesManager.TriggerEvent("CommercePayPal", "ErrorMessage", () => new Dictionary<string, object> { { "Text", messageBody } });
                    }
                }
                else { // Payment status is not "Completed"
                    string messageBody = string.Format("Incomplete Payment : Transaction ID = {0} \n\nHere is the PayPal IPN: {1}",
                        transactionID,
                        fullIPN);

                    _rulesManager.TriggerEvent("CommercePayPal", "ErrorMessage", () => new Dictionary<string, object> { { "Text", messageBody } });
                }
            }
            else { // PayPal did not respond as "VERIFIED"
                string messageBody = string.Format("Invalid Payment : Transaction ID = {0} \n\nHere is the PayPal IPN: {1}",
                        transactionID,
                        fullIPN);

                _rulesManager.TriggerEvent("CommercePayPal", "ErrorMessage", () => new Dictionary<string, object> { { "Text", messageBody } });
            }

            return View();
        }

        /// <summary>
        /// Utility method for handling PayPal Responses
        /// </summary>
        string GetPayPalResponse(Dictionary<string, string> formVals, bool useSandbox) {

            string paypalUrl = useSandbox ? "https://www.sandbox.paypal.com/cgi-bin/webscr"
                : "https://www.paypal.com/cgi-bin/webscr";

            HttpWebRequest req = (HttpWebRequest)WebRequest.Create(paypalUrl);

            // Set values for the request back
            req.Method = "POST";
            req.ContentType = "application/x-www-form-urlencoded";

            byte[] param = Request.BinaryRead(Request.ContentLength);
            string strRequest = Encoding.ASCII.GetString(param);

            StringBuilder sb = new StringBuilder();
            sb.Append(strRequest);

            foreach (string key in formVals.Keys) {
                sb.AppendFormat("&{0}={1}", key, formVals[key]);
            }
            strRequest += sb.ToString();
            req.ContentLength = strRequest.Length;

            //for proxy
            //WebProxy proxy = new WebProxy(new Uri("http://urlort#");
            //req.Proxy = proxy;
            //Send the request to PayPal and get the response
            string response = "";
            using (StreamWriter streamOut = new StreamWriter(req.GetRequestStream(), System.Text.Encoding.ASCII)) {
                streamOut.Write(strRequest);
                streamOut.Close();
                using (StreamReader streamIn = new StreamReader(req.GetResponse().GetResponseStream())) {
                    response = streamIn.ReadToEnd();
                }
            }

            return response;
        }

        /// <summary>
        /// Handles the PDT Response from PayPal
        /// </summary>
        /// <returns></returns>
        public ActionResult Returned() { // Originally called HandlePDT() {
            //_logger.Info("PDT Invoked");
            string transactionID = Request.QueryString["tx"];
            string sAmountPaid = Request.QueryString["amt"];
            string custom = Request.QueryString["cm"];
            string transactionStatus = Request.QueryString["st"];

            var payPalSettings = Services.WorkContext.CurrentSite.As<CommercePayPalSettingsPart>();

            Dictionary<string, string> formVals = new Dictionary<string, string>();
            formVals.Add("cmd", "_notify-synch");
            formVals.Add("at", payPalSettings.PDTAuthenticationToken);
            formVals.Add("tx", transactionID);

            string response = GetPayPalResponse(formVals, true);
            if (response.StartsWith("SUCCESS")) {

                //validate the order
                Decimal amountPaid = 0;
                Decimal.TryParse(sAmountPaid, out amountPaid);

                // I don't use these - here for completeness
                string firstName = GetPDTValue(response, "first_name");
                string lastName = GetPDTValue(response, "last_name");
                string payerEmail = GetPDTValue(response, "payer_email");
                string street1 = GetPDTValue(response, "address_street");
                string city = GetPDTValue(response, "address_city");
                string stateOrProvince = GetPDTValue(response, "address_state");
                string country = GetPDTValue(response, "address_country");
                string zip = GetPDTValue(response, "address_zip");
                
                // Could ensure price is what is expected, but this happens in the IPN 
                ViewData["status"] = "success";
                ViewData["message"] = "Your payment was successful with PayPal.";
            }
            else {
                ViewData["status"] = "fail";
                ViewData["message"] = "Your payment was not successful with PayPal";
            }

            return View();
        }

        string GetPDTValue(string pdt, string key) {
            string[] keys = pdt.Split('\n');
            string thisVal = "";
            string thisKey = "";
            foreach (string s in keys) {
                string[] bits = s.Split('=');
                if (bits.Length > 1) {
                    thisVal = bits[1];
                    thisKey = bits[0];
                    if (thisKey.Equals(key, StringComparison.InvariantCultureIgnoreCase))
                        break;
                }
            }
            return thisVal;
        }

        #endregion

        #endregion
    }

}
